class UpgradeRegexp{
    constructor(source,root = "root",flag){
        this.table = new Map();
        this.regexp = new RegExp(this.compileRegExp(source,root,0).source,flag);
    }
    
    exec(string){
        let r = this.regexp.exec(string)
        for (let index = 0; index < r.length; index++) {
            const element = r[index];
            if (element!==(void 0)) {
                // console.log(JSON.stringify(this.table.get(index-1)));
                r[this.table.get(index-1)] = r[index];
            }
        }
        // console.log(JSON.stringify(r[0]));
        return r;
    }

    compileRegExp(upgradeRegexp,name,start){
        
        if (upgradeRegexp[name] instanceof RegExp) {
            return {
                source: upgradeRegexp[name].source,
                length:0
            }
        }
        let length = 0;
        let regexp = upgradeRegexp[name].replace(/\<([^>]+)\>/g,(str,$1)=>{
            this.table.set(start+length,$1);
            ++length;

            let r = this.compileRegExp(upgradeRegexp,$1,start+length);
            length += r.length;
            return "("+r.source+")";
        });
        return {
            source:regexp,
            length,
        }
    }

    get lastIndex(){
        return this.regexp.lastIndex;
    }
    set lastIndex(value){
        return this.regexp.lastIndex = value;
    }
}

let upgradeRegexp = {
    InputElement:"<Whitespace>|<LineTerminator>|<Comments>|<Token>",
    Whitespace:/ /,
    LineTerminator:/\n/,
    Comments:/\/\*(?:[^*]|\*[^\/])*\*\/|\/\/[^\n]*/,
    Token:"<Literal>|<Keywords>|<Identifier>|<Punctuator>",
    Literal:"<NumericLiteral>|<BooleanLiteral>|<StringLiteral>|<NullLiteral>",
    NumericLiteral:/(?:[1-9][0-9]*|0)(?:\.[0-9]*)?/, 
    BooleanLiteral:/true|false/,
    StringLiteral:/\"(?:[^"\n]|\\[\s\S])*\"|\'(?:[^"\n]|\\[\s\S])*\'/,
    NullLiteral:/null/,
    Identifier:/[a-zA-Z_$][a-zA-Z0-9_$]*/,
    Keywords:/if|else|for|function|let|var/,
    Punctuator:/\(|=|<|\+\+|==|=>|\)|\*|\.|\[|\]|\{|\}|\.|\?|\:|;|\,|\+/
}


export function* scan(str){
    let regexp = new UpgradeRegexp(upgradeRegexp,"InputElement","g")
    while (regexp.lastIndex < str.length && regexp.lastIndex !== -1) {
        let r = regexp.exec(str);
        if (r.Whitespace) {
            
        } else if(r.LineTerminator){
            
        } else if(r.Comments){

        } else if(r.NumericLiteral){
            yield {
                type:"NumericLiteral",
                value:r[0],
            }
        } else if(r.BooleanLiteral){
            yield {
                type:"BooleanLiteral",
                value:r[0],
            }
        } else if(r.StringLiteral){
            yield {
                type:"StringLiteral",
                value:r[0],
            }
        } else if(r.NullLiteral){
            yield {
                type:"NullLiteral",
                value:null,
            }
        } else if(r.Identifier){
            yield {
                type:"Identifier",
                value:r[0],
            }
        } else if(r.Keywords){
            yield {
                type:r[0],
            }
        } else if(r.Punctuator){
            yield {
                type:r[0],
            }
        }else{
            throw new Error("unexpected token"+r[0]);
        }
        if (!r[0].length) {
            break;
        }
    }
    yield {
        type:"EOF",
    }
}

